﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using Castle.Core.Logging;
using TfsUtils.Ui.Mvvm.Command;
using TfsUtils.Ui.Mvvm.SystemLayer;
using TfsUtils.Ui.Mvvm.Ui;
using TfsUtils.Ui.Navigation;

namespace TfsUtils.Ui.Mvvm
{
    public abstract class BaseViewModel : UiObjectBase, IDisposable
    {
        internal const int MaxNumberOfMessages = 20;

        public ILogger Logger { get; set; }

        public ISystemDialogs SystemDialogs { get; set; }

        private IBroker Broker { get; set; }

        protected BaseViewModel(ISystemDialogs systemDialogs, IBroker broker)
        {
            SystemDialogs = systemDialogs;
            Broker = broker;
            Messages = new ObservableCollection<UiMessage>();
            Logger = NullLogger.Instance; //Default value, overriden by IOC
            SyncExecution = false; //di default non vogliamo l'esecuzione asincrona.
            WindowState = System.Windows.WindowState.Normal;
            InitCommand();
        }
        #region Commands 

        public DelegateCommand Close;

        public DelegateCommand Minimize;

        protected void InitCommand()
        {
           
            Minimize = DelegateCommand.Create()
               .OnCanExecute(o => WindowState != System.Windows.WindowState.Minimized)
               .TriggerUsing(PropertyChangedObserver.Monitor(this).HandleChangesOf(vm => vm.WindowState))
               .OnExecute(ExecuteMinimize);
        }

        private void ExecuteMinimize(object obj)
        {
            WindowState = System.Windows.WindowState.Minimized;
        }

        #endregion



        #region Common Properties

        /// <summary>
        /// 
        /// </summary>
        /// <value></value>
        public Boolean SuppressMessageBoxErrors
        {
            get { return _suppressMessageBoxErrors; }
            set { this.Set(p => p.SuppressMessageBoxErrors, value, ref _suppressMessageBoxErrors); }
        }

        private Boolean _suppressMessageBoxErrors;

        public ObservableCollection<UiMessage> Messages { get; set; }

        /// <summary>
        /// Di base questo viewmodel effettua un po di operazioni in modo asincrono,
        /// ma per gli unit test oppure in alcune situazioni è possibile forzare una 
        /// esecuzione sincrona
        /// </summary>
        public Boolean SyncExecution { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <value></value>
        public WindowState WindowState
        {
            get { return _windowState; }
            set { this.Set(p => p.WindowState, value, ref _windowState); }
        }

        private WindowState _windowState;
        
        protected void AddMessage(string message)
        {
            UiThread.Run(() => AddMessage(message, MvvmCommunicationType.Message));
        }

        protected virtual void AddError(string message)
        {
            UiThread.Run(() =>
            {
                AddMessage(message, MvvmCommunicationType.Error);
                if (!SuppressMessageBoxErrors) SystemDialogs.ShowError(message);
            });
        }

        private void AddMessage(String message, MvvmCommunicationType type)
        {
            UiThread.Run(() =>
            {
                Messages.Insert(0, new UiMessage(message, type));
                //la messagebox solo se è diverso da un normale messaggio e non ho la soppressione.
                if (!SuppressMessageBoxErrors && type != MvvmCommunicationType.Message)
                {
                    SystemDialogs.ShowMessageBox(message, "Message", MessageBoxImage.Information);
                }
                if (Messages.Count > MaxNumberOfMessages)
                {
                    for (int i = MaxNumberOfMessages; i < Messages.Count; i++)
                    {
                        Messages.RemoveAt(i);
                    }
                }
            });
        }

        #endregion

        #region IClientProxyEventSink

        public virtual void OnValidationFailed(object sender, ValidationFailedEventArgs args)
        {
            SystemDialogs.ShowValidationFailure(args
                .ErrorMessages
                .Select(err => err.Source + ":" + err.Message)
                .Aggregate((s1, s2) => s1 + "\n" + s2));
        }

  
        #endregion


        public virtual void Dispose(Boolean isDisposing)
        {
            if (isDisposing)
            {
                Broker.UnRegisterAll(this);
            }
        }

        protected void RegisterListenerOnBroker<T>(Action<Message<T>> dispatchMessage)
        {
            RegisterListenerOnBroker(dispatchMessage, false);
        }

        protected void RegisterListenerOnBroker<T>(Action<Message<T>> dispatchMessage, Boolean executeInUiThread)
        {
            Broker.RegisterForMessage(dispatchMessage, this, executeInUiThread);
        }

        protected void SendMessage<T>(Message<T> message)
        {
            Broker.SendMessage(message);
        }

        protected void SendMessage(String message)
        {
            Broker.SendMessage(new Message<String>(message, null));
        }

        protected void SendMessage<T>(T argument)
        {
            Broker.SendMessage(new Message<T>(String.Empty, argument));
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        protected void ExecuteInit(WaitCallback initViewModel)
        {

            ThreadPool.QueueUserWorkItem(initViewModel);
        }

        /// <summary>
        ///  Aggiunge una lista di elementi ad una <see cref="ObservableCollection{T}"/>. La necessità
        /// di avere un metodo in una classe base è assicurarsi che il tutto venga eseguito nel
        /// thread dell'interfaccia.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="target">The target.</param>
        protected void AddRangeToObservableCollection<T>(IEnumerable<T> source, ObservableCollection<T> target)
        {
            UiThread.Run(() =>
            {
                foreach (T targetLinkDto in source)
                {
                    target.Add(targetLinkDto);
                }
            });

        }

        /// <summary>
        /// AMolto spesso io ho una lista di oggetti, ma per i filtri debbo aggiungere il 
        /// valore iniziale, quello cioè dell'elemento non selezionato altrmenti il binding
        /// non funziona, per questa ragione in questo caso ammetto di inserire un ulteriore
        /// elemento in testa per indicare l'elemento nullo.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="target">The target.</param>
        /// <param name="nullObject">The null object.</param>
        protected void AddRangeToObservableCollection<T>(
            IEnumerable<T> source,
            ObservableCollection<T> target,
            T nullObject)
        {
            UiThread.Run(() =>
            {
                target.Add(nullObject);
                if (source != null)
                {
                    foreach (T targetLinkDto in source)
                    {
                        target.Add(targetLinkDto);
                    }
                }
            });

        }

        /// <summary>
        /// Esegue una azione in modalità asincrona o sincrona a seconda 
        /// della modalitaà di funzionamento impostata.
        /// </summary>
        /// <param name="action"></param>
        protected void Execute(Action action)
        {
            if (SyncExecution)
            {
                action();
            }
            else
            {
                ThreadPool.QueueUserWorkItem(o => action());
            }
        }
    }
}
